
#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include "measure.hpp"

#include <stdio.h>
#include <iostream>
#include <string>
#include <conio.h>

#define HANDLE_CUDA(x, fallback) { if ((x) != cudaSuccess) { fprintf(stderr, #x" failed."); fallback; } }
//#define CUDA(x, msg) CUDA(x, msg, )

using namespace std;

cudaError_t chooseSmartestDevice();

struct VectorAdd
{
	int size;
	int *h_c;
	int *h_a;
	int *h_b;
	int *d_c;
	int *d_a;
	int *d_b;
};

__global__ void addKernel(int *c, const int *a, const int *b, int size)
{
    int i = blockIdx.x*blockDim.x+threadIdx.x;
	if(i<size)
	{
		c[i] = a[i] + b[i];
	}
}

void cleanupVectors(VectorAdd* pVectors)
{
	delete [] pVectors->h_c;
	delete pVectors;
}

void cleanupVectors(int size, void** pData)
{
	VectorAdd *pVectors = (VectorAdd*)*pData;
	cleanupVectors(pVectors);
}

void addVectors(int size, void** pData)
{
	VectorAdd *pVectors = (VectorAdd*)*pData;

    // Allocate GPU buffers for three vectors (two input, one output).
    HANDLE_CUDA(cudaMalloc((void**)&pVectors->d_c, size * sizeof(int)), cleanupVectors(pVectors));
    HANDLE_CUDA(cudaMalloc((void**)&pVectors->d_a, size * sizeof(int)), cleanupVectors(pVectors));
    HANDLE_CUDA(cudaMalloc((void**)&pVectors->d_b, size * sizeof(int)), cleanupVectors(pVectors));

    // Copy input vectors from host memory to GPU buffers.
    HANDLE_CUDA(cudaMemset(pVectors->d_a, 1, size * sizeof(int)), cleanupVectors(pVectors));
    HANDLE_CUDA(cudaMemset(pVectors->d_b, 2, size * sizeof(int)), cleanupVectors(pVectors));

	addKernel<<<1, size>>>(pVectors->d_c, pVectors->d_a, pVectors->d_b, size);

    HANDLE_CUDA(cudaFree(pVectors->d_c));
    HANDLE_CUDA(cudaFree(pVectors->d_a));
    HANDLE_CUDA(cudaFree(pVectors->d_b));

    HANDLE_CUDA(cudaMemcpy(pVectors->h_c, pVectors->d_c, pVectors->size*sizeof(int), cudaMemcpyDeviceToHost));
	HANDLE_CUDA(cudaThreadSynchronize());
}

void initVectors(int size, void** pData)
{
	VectorAdd* pVectors = new VectorAdd();
	pVectors->size = size;
	cout << "Allocating 3 * " << size << " * " << sizeof(int) << " = " << (3*size*sizeof(int))/1024ul*1204ul*1024ul << " GB." << endl;
	
	pVectors->h_c = new int[pVectors->size];

	*pData = pVectors;
	return;
}

int main()
{
	chooseSmartestDevice();
	Measure measure;
	measure.setOutstream("zeitmessung.txt");
	measure.setDebugOut(cout);
	measure.addFunction("cuda", addVectors, initVectors, cleanupVectors);
	measure.addNewMeasureInterval(1000, 100000, 990000);
	measure.addNewMeasureInterval(990000, 1000000, 700000000);//178956970
	measure.setRepetitions(5);
	measure.run();
	getch();
}

cudaError_t chooseSmartestDevice()
{
    cudaError_t cudaStatus;

	int deviceId;
	string deviceName;
    int deviceCount = 0;
    cudaError_t error_id = cudaGetDeviceCount(&deviceCount);

    if (error_id != cudaSuccess)
    {
        printf("cudaGetDeviceCount returned %d\n-> %s\n", (int)error_id, cudaGetErrorString(error_id));
        exit(EXIT_FAILURE);
    }

    // This function call returns 0 if there are no CUDA capable devices.
    if (deviceCount == 0)
    {
        printf("There are no available device(s) that support CUDA\n");
    }
    else
    {
        printf("Detected %d CUDA Capable device(s)\n", deviceCount);
    }

	int major = 0;
	int minor = 0;
	for (int dev = 0; dev < deviceCount; ++dev)
    {
        cudaDeviceProp deviceProp;
        cudaGetDeviceProperties(&deviceProp, dev);

        printf("\nDevice %d: \"%s\"\n", dev, deviceProp.name);
		if(deviceProp.major > major || (deviceProp.major == major && deviceProp.minor > minor))
		{
			major = deviceProp.major;
			minor = deviceProp.minor;
			deviceId = dev;
			deviceName = deviceProp.name;
		}
	}
	printf("\nCoosing your coolest CUDA device: %d: \"%s\" (highest compute capability)\n", deviceId, deviceName.c_str());
    // Choose which GPU to run on, change this on a multi-GPU system.
    cudaStatus = cudaSetDevice(deviceId);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
    }
	return cudaStatus;
}
